Browse Source

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
tags/v2.9.0-rc1
Shivaram Lingamneni 2 years ago
parent
commit
ed75533cb1
No account linked to committer's email address

+ 6
- 0
default.yaml View File

@@ -746,6 +746,12 @@ debug:
746 746
     # set to `null`, "", leave blank, or omit to disable
747 747
     # pprof-listener: "localhost:6060"
748 748
 
749
+# lock file preventing multiple instances of Ergo from accidentally being
750
+# started at once. comment out or set to the empty string ("") to disable.
751
+# this path is relative to the working directory; you may want to use an
752
+# absolute path instead:
753
+lock-file: "ircd.lock"
754
+
749 755
 # datastore configuration
750 756
 datastore:
751 757
     # path to the datastore

+ 2
- 0
go.mod View File

@@ -25,6 +25,8 @@ require (
25 25
 	gopkg.in/yaml.v2 v2.4.0
26 26
 )
27 27
 
28
+require github.com/gofrs/flock v0.8.1
29
+
28 30
 require (
29 31
 	github.com/tidwall/btree v0.6.1 // indirect
30 32
 	github.com/tidwall/gjson v1.10.2 // indirect

+ 2
- 0
go.sum View File

@@ -21,6 +21,8 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC
21 21
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
22 22
 github.com/go-test/deep v1.0.6 h1:UHSEyLZUwX9Qoi99vVwvewiMC8mM2bf7XEM2nqvzEn8=
23 23
 github.com/go-test/deep v1.0.6/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
24
+github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
25
+github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
24 26
 github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
25 27
 github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
26 28
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=

+ 2
- 0
irc/config.go View File

@@ -616,6 +616,8 @@ type Config struct {
616 616
 
617 617
 	languageManager *languages.Manager
618 618
 
619
+	LockFile string `yaml:"lock-file"`
620
+
619 621
 	Datastore struct {
620 622
 		Path        string
621 623
 		AutoUpgrade bool

+ 24
- 0
irc/flock/flock.go View File

@@ -0,0 +1,24 @@
1
+//go:build !plan9
2
+
3
+package flock
4
+
5
+import (
6
+	"errors"
7
+
8
+	"github.com/gofrs/flock"
9
+)
10
+
11
+var (
12
+	CouldntAcquire = errors.New("Couldn't acquire flock (is another Ergo running?)")
13
+)
14
+
15
+func TryAcquireFlock(path string) (fl Flocker, err error) {
16
+	f := flock.New(path)
17
+	success, err := f.TryLock()
18
+	if err != nil {
19
+		return nil, err
20
+	} else if !success {
21
+		return nil, CouldntAcquire
22
+	}
23
+	return f, nil
24
+}

+ 14
- 0
irc/flock/flock_iface.go View File

@@ -0,0 +1,14 @@
1
+package flock
2
+
3
+// documentation for github.com/gofrs/flock incorrectly claims that
4
+// Flock implements sync.Locker; it does not because the Unlock method
5
+// has a return type (err).
6
+type Flocker interface {
7
+	Unlock() error
8
+}
9
+
10
+type noopFlocker struct{}
11
+
12
+func (n *noopFlocker) Unlock() error {
13
+	return nil
14
+}

+ 7
- 0
irc/flock/flock_plan9.go View File

@@ -0,0 +1,7 @@
1
+//go:build plan9
2
+
3
+package flock
4
+
5
+func TryAcquireFlock(path string) (fl Flocker, err error) {
6
+	return &noopFlocker{}, nil
7
+}

+ 15
- 0
irc/server.go View File

@@ -25,6 +25,7 @@ import (
25 25
 	"github.com/ergochat/ergo/irc/caps"
26 26
 	"github.com/ergochat/ergo/irc/connection_limits"
27 27
 	"github.com/ergochat/ergo/irc/flatip"
28
+	"github.com/ergochat/ergo/irc/flock"
28 29
 	"github.com/ergochat/ergo/irc/history"
29 30
 	"github.com/ergochat/ergo/irc/logger"
30 31
 	"github.com/ergochat/ergo/irc/modes"
@@ -88,6 +89,7 @@ type Server struct {
88 89
 	whoWas            WhoWasList
89 90
 	stats             Stats
90 91
 	semaphores        ServerSemaphores
92
+	flock             flock.Flocker
91 93
 	defcon            uint32
92 94
 }
93 95
 
@@ -585,6 +587,19 @@ func (server *Server) applyConfig(config *Config) (err error) {
585 587
 
586 588
 	server.logger.Info("server", "Using config file", server.configFilename)
587 589
 
590
+	if initial {
591
+		if config.LockFile != "" {
592
+			server.flock, err = flock.TryAcquireFlock(config.LockFile)
593
+			if err != nil {
594
+				return fmt.Errorf("failed to acquire flock on %s: %w",
595
+					config.LockFile, err)
596
+			}
597
+		}
598
+		// the lock is never released until quit; we need to save a pointer
599
+		// to the (*flock.Flock) object so it doesn't get GC'ed, which would
600
+		// close the file and surrender the lock
601
+	}
602
+
588 603
 	// first, reload config sections for functionality implemented in subpackages:
589 604
 	wasLoggingRawIO := !initial && server.logger.IsLoggingRawIO()
590 605
 	err = server.logger.ApplyConfig(config.Logging)

+ 6
- 0
traditional.yaml View File

@@ -718,6 +718,12 @@ debug:
718 718
     # set to `null`, "", leave blank, or omit to disable
719 719
     # pprof-listener: "localhost:6060"
720 720
 
721
+# lock file preventing multiple instances of Ergo from accidentally being
722
+# started at once. comment out or set to the empty string ("") to disable.
723
+# this path is relative to the working directory; you may want to use an
724
+# absolute path instead:
725
+lock-file: "ircd.lock"
726
+
721 727
 # datastore configuration
722 728
 datastore:
723 729
     # path to the datastore

+ 24
- 0
vendor/github.com/gofrs/flock/.gitignore View File

@@ -0,0 +1,24 @@
1
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
2
+*.o
3
+*.a
4
+*.so
5
+
6
+# Folders
7
+_obj
8
+_test
9
+
10
+# Architecture specific extensions/prefixes
11
+*.[568vq]
12
+[568vq].out
13
+
14
+*.cgo1.go
15
+*.cgo2.c
16
+_cgo_defun.c
17
+_cgo_gotypes.go
18
+_cgo_export.*
19
+
20
+_testmain.go
21
+
22
+*.exe
23
+*.test
24
+*.prof

+ 10
- 0
vendor/github.com/gofrs/flock/.travis.yml View File

@@ -0,0 +1,10 @@
1
+language: go
2
+go:
3
+  - 1.14.x
4
+  - 1.15.x
5
+script: go test -v -check.vv -race ./...
6
+sudo: false
7
+notifications:
8
+  email:
9
+    on_success: never
10
+    on_failure: always

+ 27
- 0
vendor/github.com/gofrs/flock/LICENSE View File

@@ -0,0 +1,27 @@
1
+Copyright (c) 2015-2020, Tim Heckman
2
+All rights reserved.
3
+
4
+Redistribution and use in source and binary forms, with or without
5
+modification, are permitted provided that the following conditions are met:
6
+
7
+* Redistributions of source code must retain the above copyright notice, this
8
+  list of conditions and the following disclaimer.
9
+
10
+* Redistributions in binary form must reproduce the above copyright notice,
11
+  this list of conditions and the following disclaimer in the documentation
12
+  and/or other materials provided with the distribution.
13
+
14
+* Neither the name of gofrs nor the names of its contributors may be used
15
+  to endorse or promote products derived from this software without
16
+  specific prior written permission.
17
+
18
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 41
- 0
vendor/github.com/gofrs/flock/README.md View File

@@ -0,0 +1,41 @@
1
+# flock
2
+[![TravisCI Build Status](https://img.shields.io/travis/gofrs/flock/master.svg?style=flat)](https://travis-ci.org/gofrs/flock)
3
+[![GoDoc](https://img.shields.io/badge/godoc-flock-blue.svg?style=flat)](https://godoc.org/github.com/gofrs/flock)
4
+[![License](https://img.shields.io/badge/license-BSD_3--Clause-brightgreen.svg?style=flat)](https://github.com/gofrs/flock/blob/master/LICENSE)
5
+[![Go Report Card](https://goreportcard.com/badge/github.com/gofrs/flock)](https://goreportcard.com/report/github.com/gofrs/flock)
6
+
7
+`flock` implements a thread-safe sync.Locker interface for file locking. It also
8
+includes a non-blocking TryLock() function to allow locking without blocking execution.
9
+
10
+## License
11
+`flock` is released under the BSD 3-Clause License. See the `LICENSE` file for more details.
12
+
13
+## Go Compatibility
14
+This package makes use of the `context` package that was introduced in Go 1.7. As such, this
15
+package has an implicit dependency on Go 1.7+.
16
+
17
+## Installation
18
+```
19
+go get -u github.com/gofrs/flock
20
+```
21
+
22
+## Usage
23
+```Go
24
+import "github.com/gofrs/flock"
25
+
26
+fileLock := flock.New("/var/lock/go-lock.lock")
27
+
28
+locked, err := fileLock.TryLock()
29
+
30
+if err != nil {
31
+	// handle locking error
32
+}
33
+
34
+if locked {
35
+	// do work
36
+	fileLock.Unlock()
37
+}
38
+```
39
+
40
+For more detailed usage information take a look at the package API docs on
41
+[GoDoc](https://godoc.org/github.com/gofrs/flock).

+ 25
- 0
vendor/github.com/gofrs/flock/appveyor.yml View File

@@ -0,0 +1,25 @@
1
+version: '{build}'
2
+
3
+build: false
4
+deploy: false
5
+
6
+clone_folder: 'c:\gopath\src\github.com\gofrs\flock'
7
+
8
+environment:
9
+  GOPATH: 'c:\gopath'
10
+  GOVERSION: '1.15'
11
+
12
+init:
13
+  - git config --global core.autocrlf input
14
+
15
+install:
16
+  - rmdir c:\go /s /q
17
+  - appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.msi
18
+  - msiexec /i go%GOVERSION%.windows-amd64.msi /q
19
+  - set Path=c:\go\bin;c:\gopath\bin;%Path%
20
+  - go version
21
+  - go env
22
+
23
+test_script:
24
+  - go get -t ./...
25
+  - go test -race -v ./...

+ 144
- 0
vendor/github.com/gofrs/flock/flock.go View File

@@ -0,0 +1,144 @@
1
+// Copyright 2015 Tim Heckman. All rights reserved.
2
+// Use of this source code is governed by the BSD 3-Clause
3
+// license that can be found in the LICENSE file.
4
+
5
+// Package flock implements a thread-safe interface for file locking.
6
+// It also includes a non-blocking TryLock() function to allow locking
7
+// without blocking execution.
8
+//
9
+// Package flock is released under the BSD 3-Clause License. See the LICENSE file
10
+// for more details.
11
+//
12
+// While using this library, remember that the locking behaviors are not
13
+// guaranteed to be the same on each platform. For example, some UNIX-like
14
+// operating systems will transparently convert a shared lock to an exclusive
15
+// lock. If you Unlock() the flock from a location where you believe that you
16
+// have the shared lock, you may accidentally drop the exclusive lock.
17
+package flock
18
+
19
+import (
20
+	"context"
21
+	"os"
22
+	"runtime"
23
+	"sync"
24
+	"time"
25
+)
26
+
27
+// Flock is the struct type to handle file locking. All fields are unexported,
28
+// with access to some of the fields provided by getter methods (Path() and Locked()).
29
+type Flock struct {
30
+	path string
31
+	m    sync.RWMutex
32
+	fh   *os.File
33
+	l    bool
34
+	r    bool
35
+}
36
+
37
+// New returns a new instance of *Flock. The only parameter
38
+// it takes is the path to the desired lockfile.
39
+func New(path string) *Flock {
40
+	return &Flock{path: path}
41
+}
42
+
43
+// NewFlock returns a new instance of *Flock. The only parameter
44
+// it takes is the path to the desired lockfile.
45
+//
46
+// Deprecated: Use New instead.
47
+func NewFlock(path string) *Flock {
48
+	return New(path)
49
+}
50
+
51
+// Close is equivalent to calling Unlock.
52
+//
53
+// This will release the lock and close the underlying file descriptor.
54
+// It will not remove the file from disk, that's up to your application.
55
+func (f *Flock) Close() error {
56
+	return f.Unlock()
57
+}
58
+
59
+// Path returns the path as provided in NewFlock().
60
+func (f *Flock) Path() string {
61
+	return f.path
62
+}
63
+
64
+// Locked returns the lock state (locked: true, unlocked: false).
65
+//
66
+// Warning: by the time you use the returned value, the state may have changed.
67
+func (f *Flock) Locked() bool {
68
+	f.m.RLock()
69
+	defer f.m.RUnlock()
70
+	return f.l
71
+}
72
+
73
+// RLocked returns the read lock state (locked: true, unlocked: false).
74
+//
75
+// Warning: by the time you use the returned value, the state may have changed.
76
+func (f *Flock) RLocked() bool {
77
+	f.m.RLock()
78
+	defer f.m.RUnlock()
79
+	return f.r
80
+}
81
+
82
+func (f *Flock) String() string {
83
+	return f.path
84
+}
85
+
86
+// TryLockContext repeatedly tries to take an exclusive lock until one of the
87
+// conditions is met: TryLock succeeds, TryLock fails with error, or Context
88
+// Done channel is closed.
89
+func (f *Flock) TryLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) {
90
+	return tryCtx(ctx, f.TryLock, retryDelay)
91
+}
92
+
93
+// TryRLockContext repeatedly tries to take a shared lock until one of the
94
+// conditions is met: TryRLock succeeds, TryRLock fails with error, or Context
95
+// Done channel is closed.
96
+func (f *Flock) TryRLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) {
97
+	return tryCtx(ctx, f.TryRLock, retryDelay)
98
+}
99
+
100
+func tryCtx(ctx context.Context, fn func() (bool, error), retryDelay time.Duration) (bool, error) {
101
+	if ctx.Err() != nil {
102
+		return false, ctx.Err()
103
+	}
104
+	for {
105
+		if ok, err := fn(); ok || err != nil {
106
+			return ok, err
107
+		}
108
+		select {
109
+		case <-ctx.Done():
110
+			return false, ctx.Err()
111
+		case <-time.After(retryDelay):
112
+			// try again
113
+		}
114
+	}
115
+}
116
+
117
+func (f *Flock) setFh() error {
118
+	// open a new os.File instance
119
+	// create it if it doesn't exist, and open the file read-only.
120
+	flags := os.O_CREATE
121
+	if runtime.GOOS == "aix" {
122
+		// AIX cannot preform write-lock (ie exclusive) on a
123
+		// read-only file.
124
+		flags |= os.O_RDWR
125
+	} else {
126
+		flags |= os.O_RDONLY
127
+	}
128
+	fh, err := os.OpenFile(f.path, flags, os.FileMode(0600))
129
+	if err != nil {
130
+		return err
131
+	}
132
+
133
+	// set the filehandle on the struct
134
+	f.fh = fh
135
+	return nil
136
+}
137
+
138
+// ensure the file handle is closed if no lock is held
139
+func (f *Flock) ensureFhState() {
140
+	if !f.l && !f.r && f.fh != nil {
141
+		f.fh.Close()
142
+		f.fh = nil
143
+	}
144
+}

+ 281
- 0
vendor/github.com/gofrs/flock/flock_aix.go View File

@@ -0,0 +1,281 @@
1
+// Copyright 2019 Tim Heckman. All rights reserved. Use of this source code is
2
+// governed by the BSD 3-Clause license that can be found in the LICENSE file.
3
+
4
+// Copyright 2018 The Go Authors. All rights reserved.
5
+// Use of this source code is governed by a BSD-style
6
+// license that can be found in the LICENSE file.
7
+
8
+// This code implements the filelock API using POSIX 'fcntl' locks, which attach
9
+// to an (inode, process) pair rather than a file descriptor. To avoid unlocking
10
+// files prematurely when the same file is opened through different descriptors,
11
+// we allow only one read-lock at a time.
12
+//
13
+// This code is adapted from the Go package:
14
+// cmd/go/internal/lockedfile/internal/filelock
15
+
16
+//+build aix
17
+
18
+package flock
19
+
20
+import (
21
+	"errors"
22
+	"io"
23
+	"os"
24
+	"sync"
25
+	"syscall"
26
+
27
+	"golang.org/x/sys/unix"
28
+)
29
+
30
+type lockType int16
31
+
32
+const (
33
+	readLock  lockType = unix.F_RDLCK
34
+	writeLock lockType = unix.F_WRLCK
35
+)
36
+
37
+type cmdType int
38
+
39
+const (
40
+	tryLock  cmdType = unix.F_SETLK
41
+	waitLock cmdType = unix.F_SETLKW
42
+)
43
+
44
+type inode = uint64
45
+
46
+type inodeLock struct {
47
+	owner *Flock
48
+	queue []<-chan *Flock
49
+}
50
+
51
+var (
52
+	mu     sync.Mutex
53
+	inodes = map[*Flock]inode{}
54
+	locks  = map[inode]inodeLock{}
55
+)
56
+
57
+// Lock is a blocking call to try and take an exclusive file lock. It will wait
58
+// until it is able to obtain the exclusive file lock. It's recommended that
59
+// TryLock() be used over this function. This function may block the ability to
60
+// query the current Locked() or RLocked() status due to a RW-mutex lock.
61
+//
62
+// If we are already exclusive-locked, this function short-circuits and returns
63
+// immediately assuming it can take the mutex lock.
64
+//
65
+// If the *Flock has a shared lock (RLock), this may transparently replace the
66
+// shared lock with an exclusive lock on some UNIX-like operating systems. Be
67
+// careful when using exclusive locks in conjunction with shared locks
68
+// (RLock()), because calling Unlock() may accidentally release the exclusive
69
+// lock that was once a shared lock.
70
+func (f *Flock) Lock() error {
71
+	return f.lock(&f.l, writeLock)
72
+}
73
+
74
+// RLock is a blocking call to try and take a shared file lock. It will wait
75
+// until it is able to obtain the shared file lock. It's recommended that
76
+// TryRLock() be used over this function. This function may block the ability to
77
+// query the current Locked() or RLocked() status due to a RW-mutex lock.
78
+//
79
+// If we are already shared-locked, this function short-circuits and returns
80
+// immediately assuming it can take the mutex lock.
81
+func (f *Flock) RLock() error {
82
+	return f.lock(&f.r, readLock)
83
+}
84
+
85
+func (f *Flock) lock(locked *bool, flag lockType) error {
86
+	f.m.Lock()
87
+	defer f.m.Unlock()
88
+
89
+	if *locked {
90
+		return nil
91
+	}
92
+
93
+	if f.fh == nil {
94
+		if err := f.setFh(); err != nil {
95
+			return err
96
+		}
97
+		defer f.ensureFhState()
98
+	}
99
+
100
+	if _, err := f.doLock(waitLock, flag, true); err != nil {
101
+		return err
102
+	}
103
+
104
+	*locked = true
105
+	return nil
106
+}
107
+
108
+func (f *Flock) doLock(cmd cmdType, lt lockType, blocking bool) (bool, error) {
109
+	// POSIX locks apply per inode and process, and the lock for an inode is
110
+	// released when *any* descriptor for that inode is closed. So we need to
111
+	// synchronize access to each inode internally, and must serialize lock and
112
+	// unlock calls that refer to the same inode through different descriptors.
113
+	fi, err := f.fh.Stat()
114
+	if err != nil {
115
+		return false, err
116
+	}
117
+	ino := inode(fi.Sys().(*syscall.Stat_t).Ino)
118
+
119
+	mu.Lock()
120
+	if i, dup := inodes[f]; dup && i != ino {
121
+		mu.Unlock()
122
+		return false, &os.PathError{
123
+			Path: f.Path(),
124
+			Err:  errors.New("inode for file changed since last Lock or RLock"),
125
+		}
126
+	}
127
+
128
+	inodes[f] = ino
129
+
130
+	var wait chan *Flock
131
+	l := locks[ino]
132
+	if l.owner == f {
133
+		// This file already owns the lock, but the call may change its lock type.
134
+	} else if l.owner == nil {
135
+		// No owner: it's ours now.
136
+		l.owner = f
137
+	} else if !blocking {
138
+		// Already owned: cannot take the lock.
139
+		mu.Unlock()
140
+		return false, nil
141
+	} else {
142
+		// Already owned: add a channel to wait on.
143
+		wait = make(chan *Flock)
144
+		l.queue = append(l.queue, wait)
145
+	}
146
+	locks[ino] = l
147
+	mu.Unlock()
148
+
149
+	if wait != nil {
150
+		wait <- f
151
+	}
152
+
153
+	err = setlkw(f.fh.Fd(), cmd, lt)
154
+
155
+	if err != nil {
156
+		f.doUnlock()
157
+		if cmd == tryLock && err == unix.EACCES {
158
+			return false, nil
159
+		}
160
+		return false, err
161
+	}
162
+
163
+	return true, nil
164
+}
165
+
166
+func (f *Flock) Unlock() error {
167
+	f.m.Lock()
168
+	defer f.m.Unlock()
169
+
170
+	// if we aren't locked or if the lockfile instance is nil
171
+	// just return a nil error because we are unlocked
172
+	if (!f.l && !f.r) || f.fh == nil {
173
+		return nil
174
+	}
175
+
176
+	if err := f.doUnlock(); err != nil {
177
+		return err
178
+	}
179
+
180
+	f.fh.Close()
181
+
182
+	f.l = false
183
+	f.r = false
184
+	f.fh = nil
185
+
186
+	return nil
187
+}
188
+
189
+func (f *Flock) doUnlock() (err error) {
190
+	var owner *Flock
191
+	mu.Lock()
192
+	ino, ok := inodes[f]
193
+	if ok {
194
+		owner = locks[ino].owner
195
+	}
196
+	mu.Unlock()
197
+
198
+	if owner == f {
199
+		err = setlkw(f.fh.Fd(), waitLock, unix.F_UNLCK)
200
+	}
201
+
202
+	mu.Lock()
203
+	l := locks[ino]
204
+	if len(l.queue) == 0 {
205
+		// No waiters: remove the map entry.
206
+		delete(locks, ino)
207
+	} else {
208
+		// The first waiter is sending us their file now.
209
+		// Receive it and update the queue.
210
+		l.owner = <-l.queue[0]
211
+		l.queue = l.queue[1:]
212
+		locks[ino] = l
213
+	}
214
+	delete(inodes, f)
215
+	mu.Unlock()
216
+
217
+	return err
218
+}
219
+
220
+// TryLock is the preferred function for taking an exclusive file lock. This
221
+// function takes an RW-mutex lock before it tries to lock the file, so there is
222
+// the possibility that this function may block for a short time if another
223
+// goroutine is trying to take any action.
224
+//
225
+// The actual file lock is non-blocking. If we are unable to get the exclusive
226
+// file lock, the function will return false instead of waiting for the lock. If
227
+// we get the lock, we also set the *Flock instance as being exclusive-locked.
228
+func (f *Flock) TryLock() (bool, error) {
229
+	return f.try(&f.l, writeLock)
230
+}
231
+
232
+// TryRLock is the preferred function for taking a shared file lock. This
233
+// function takes an RW-mutex lock before it tries to lock the file, so there is
234
+// the possibility that this function may block for a short time if another
235
+// goroutine is trying to take any action.
236
+//
237
+// The actual file lock is non-blocking. If we are unable to get the shared file
238
+// lock, the function will return false instead of waiting for the lock. If we
239
+// get the lock, we also set the *Flock instance as being share-locked.
240
+func (f *Flock) TryRLock() (bool, error) {
241
+	return f.try(&f.r, readLock)
242
+}
243
+
244
+func (f *Flock) try(locked *bool, flag lockType) (bool, error) {
245
+	f.m.Lock()
246
+	defer f.m.Unlock()
247
+
248
+	if *locked {
249
+		return true, nil
250
+	}
251
+
252
+	if f.fh == nil {
253
+		if err := f.setFh(); err != nil {
254
+			return false, err
255
+		}
256
+		defer f.ensureFhState()
257
+	}
258
+
259
+	haslock, err := f.doLock(tryLock, flag, false)
260
+	if err != nil {
261
+		return false, err
262
+	}
263
+
264
+	*locked = haslock
265
+	return haslock, nil
266
+}
267
+
268
+// setlkw calls FcntlFlock with cmd for the entire file indicated by fd.
269
+func setlkw(fd uintptr, cmd cmdType, lt lockType) error {
270
+	for {
271
+		err := unix.FcntlFlock(fd, int(cmd), &unix.Flock_t{
272
+			Type:   int16(lt),
273
+			Whence: io.SeekStart,
274
+			Start:  0,
275
+			Len:    0, // All bytes.
276
+		})
277
+		if err != unix.EINTR {
278
+			return err
279
+		}
280
+	}
281
+}

+ 197
- 0
vendor/github.com/gofrs/flock/flock_unix.go View File

@@ -0,0 +1,197 @@
1
+// Copyright 2015 Tim Heckman. All rights reserved.
2
+// Use of this source code is governed by the BSD 3-Clause
3
+// license that can be found in the LICENSE file.
4
+
5
+// +build !aix,!windows
6
+
7
+package flock
8
+
9
+import (
10
+	"os"
11
+	"syscall"
12
+)
13
+
14
+// Lock is a blocking call to try and take an exclusive file lock. It will wait
15
+// until it is able to obtain the exclusive file lock. It's recommended that
16
+// TryLock() be used over this function. This function may block the ability to
17
+// query the current Locked() or RLocked() status due to a RW-mutex lock.
18
+//
19
+// If we are already exclusive-locked, this function short-circuits and returns
20
+// immediately assuming it can take the mutex lock.
21
+//
22
+// If the *Flock has a shared lock (RLock), this may transparently replace the
23
+// shared lock with an exclusive lock on some UNIX-like operating systems. Be
24
+// careful when using exclusive locks in conjunction with shared locks
25
+// (RLock()), because calling Unlock() may accidentally release the exclusive
26
+// lock that was once a shared lock.
27
+func (f *Flock) Lock() error {
28
+	return f.lock(&f.l, syscall.LOCK_EX)
29
+}
30
+
31
+// RLock is a blocking call to try and take a shared file lock. It will wait
32
+// until it is able to obtain the shared file lock. It's recommended that
33
+// TryRLock() be used over this function. This function may block the ability to
34
+// query the current Locked() or RLocked() status due to a RW-mutex lock.
35
+//
36
+// If we are already shared-locked, this function short-circuits and returns
37
+// immediately assuming it can take the mutex lock.
38
+func (f *Flock) RLock() error {
39
+	return f.lock(&f.r, syscall.LOCK_SH)
40
+}
41
+
42
+func (f *Flock) lock(locked *bool, flag int) error {
43
+	f.m.Lock()
44
+	defer f.m.Unlock()
45
+
46
+	if *locked {
47
+		return nil
48
+	}
49
+
50
+	if f.fh == nil {
51
+		if err := f.setFh(); err != nil {
52
+			return err
53
+		}
54
+		defer f.ensureFhState()
55
+	}
56
+
57
+	if err := syscall.Flock(int(f.fh.Fd()), flag); err != nil {
58
+		shouldRetry, reopenErr := f.reopenFDOnError(err)
59
+		if reopenErr != nil {
60
+			return reopenErr
61
+		}
62
+
63
+		if !shouldRetry {
64
+			return err
65
+		}
66
+
67
+		if err = syscall.Flock(int(f.fh.Fd()), flag); err != nil {
68
+			return err
69
+		}
70
+	}
71
+
72
+	*locked = true
73
+	return nil
74
+}
75
+
76
+// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so
77
+// while it is running the Locked() and RLocked() functions will be blocked.
78
+//
79
+// This function short-circuits if we are unlocked already. If not, it calls
80
+// syscall.LOCK_UN on the file and closes the file descriptor. It does not
81
+// remove the file from disk. It's up to your application to do.
82
+//
83
+// Please note, if your shared lock became an exclusive lock this may
84
+// unintentionally drop the exclusive lock if called by the consumer that
85
+// believes they have a shared lock. Please see Lock() for more details.
86
+func (f *Flock) Unlock() error {
87
+	f.m.Lock()
88
+	defer f.m.Unlock()
89
+
90
+	// if we aren't locked or if the lockfile instance is nil
91
+	// just return a nil error because we are unlocked
92
+	if (!f.l && !f.r) || f.fh == nil {
93
+		return nil
94
+	}
95
+
96
+	// mark the file as unlocked
97
+	if err := syscall.Flock(int(f.fh.Fd()), syscall.LOCK_UN); err != nil {
98
+		return err
99
+	}
100
+
101
+	f.fh.Close()
102
+
103
+	f.l = false
104
+	f.r = false
105
+	f.fh = nil
106
+
107
+	return nil
108
+}
109
+
110
+// TryLock is the preferred function for taking an exclusive file lock. This
111
+// function takes an RW-mutex lock before it tries to lock the file, so there is
112
+// the possibility that this function may block for a short time if another
113
+// goroutine is trying to take any action.
114
+//
115
+// The actual file lock is non-blocking. If we are unable to get the exclusive
116
+// file lock, the function will return false instead of waiting for the lock. If
117
+// we get the lock, we also set the *Flock instance as being exclusive-locked.
118
+func (f *Flock) TryLock() (bool, error) {
119
+	return f.try(&f.l, syscall.LOCK_EX)
120
+}
121
+
122
+// TryRLock is the preferred function for taking a shared file lock. This
123
+// function takes an RW-mutex lock before it tries to lock the file, so there is
124
+// the possibility that this function may block for a short time if another
125
+// goroutine is trying to take any action.
126
+//
127
+// The actual file lock is non-blocking. If we are unable to get the shared file
128
+// lock, the function will return false instead of waiting for the lock. If we
129
+// get the lock, we also set the *Flock instance as being share-locked.
130
+func (f *Flock) TryRLock() (bool, error) {
131
+	return f.try(&f.r, syscall.LOCK_SH)
132
+}
133
+
134
+func (f *Flock) try(locked *bool, flag int) (bool, error) {
135
+	f.m.Lock()
136
+	defer f.m.Unlock()
137
+
138
+	if *locked {
139
+		return true, nil
140
+	}
141
+
142
+	if f.fh == nil {
143
+		if err := f.setFh(); err != nil {
144
+			return false, err
145
+		}
146
+		defer f.ensureFhState()
147
+	}
148
+
149
+	var retried bool
150
+retry:
151
+	err := syscall.Flock(int(f.fh.Fd()), flag|syscall.LOCK_NB)
152
+
153
+	switch err {
154
+	case syscall.EWOULDBLOCK:
155
+		return false, nil
156
+	case nil:
157
+		*locked = true
158
+		return true, nil
159
+	}
160
+	if !retried {
161
+		if shouldRetry, reopenErr := f.reopenFDOnError(err); reopenErr != nil {
162
+			return false, reopenErr
163
+		} else if shouldRetry {
164
+			retried = true
165
+			goto retry
166
+		}
167
+	}
168
+
169
+	return false, err
170
+}
171
+
172
+// reopenFDOnError determines whether we should reopen the file handle
173
+// in readwrite mode and try again. This comes from util-linux/sys-utils/flock.c:
174
+//  Since Linux 3.4 (commit 55725513)
175
+//  Probably NFSv4 where flock() is emulated by fcntl().
176
+func (f *Flock) reopenFDOnError(err error) (bool, error) {
177
+	if err != syscall.EIO && err != syscall.EBADF {
178
+		return false, nil
179
+	}
180
+	if st, err := f.fh.Stat(); err == nil {
181
+		// if the file is able to be read and written
182
+		if st.Mode()&0600 == 0600 {
183
+			f.fh.Close()
184
+			f.fh = nil
185
+
186
+			// reopen in read-write mode and set the filehandle
187
+			fh, err := os.OpenFile(f.path, os.O_CREATE|os.O_RDWR, os.FileMode(0600))
188
+			if err != nil {
189
+				return false, err
190
+			}
191
+			f.fh = fh
192
+			return true, nil
193
+		}
194
+	}
195
+
196
+	return false, nil
197
+}

+ 76
- 0
vendor/github.com/gofrs/flock/flock_winapi.go View File

@@ -0,0 +1,76 @@
1
+// Copyright 2015 Tim Heckman. All rights reserved.
2
+// Use of this source code is governed by the BSD 3-Clause
3
+// license that can be found in the LICENSE file.
4
+
5
+// +build windows
6
+
7
+package flock
8
+
9
+import (
10
+	"syscall"
11
+	"unsafe"
12
+)
13
+
14
+var (
15
+	kernel32, _         = syscall.LoadLibrary("kernel32.dll")
16
+	procLockFileEx, _   = syscall.GetProcAddress(kernel32, "LockFileEx")
17
+	procUnlockFileEx, _ = syscall.GetProcAddress(kernel32, "UnlockFileEx")
18
+)
19
+
20
+const (
21
+	winLockfileFailImmediately = 0x00000001
22
+	winLockfileExclusiveLock   = 0x00000002
23
+	winLockfileSharedLock      = 0x00000000
24
+)
25
+
26
+// Use of 0x00000000 for the shared lock is a guess based on some the MS Windows
27
+// `LockFileEX` docs, which document the `LOCKFILE_EXCLUSIVE_LOCK` flag as:
28
+//
29
+// > The function requests an exclusive lock. Otherwise, it requests a shared
30
+// > lock.
31
+//
32
+// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx
33
+
34
+func lockFileEx(handle syscall.Handle, flags uint32, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) {
35
+	r1, _, errNo := syscall.Syscall6(
36
+		uintptr(procLockFileEx),
37
+		6,
38
+		uintptr(handle),
39
+		uintptr(flags),
40
+		uintptr(reserved),
41
+		uintptr(numberOfBytesToLockLow),
42
+		uintptr(numberOfBytesToLockHigh),
43
+		uintptr(unsafe.Pointer(offset)))
44
+
45
+	if r1 != 1 {
46
+		if errNo == 0 {
47
+			return false, syscall.EINVAL
48
+		}
49
+
50
+		return false, errNo
51
+	}
52
+
53
+	return true, 0
54
+}
55
+
56
+func unlockFileEx(handle syscall.Handle, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) {
57
+	r1, _, errNo := syscall.Syscall6(
58
+		uintptr(procUnlockFileEx),
59
+		5,
60
+		uintptr(handle),
61
+		uintptr(reserved),
62
+		uintptr(numberOfBytesToLockLow),
63
+		uintptr(numberOfBytesToLockHigh),
64
+		uintptr(unsafe.Pointer(offset)),
65
+		0)
66
+
67
+	if r1 != 1 {
68
+		if errNo == 0 {
69
+			return false, syscall.EINVAL
70
+		}
71
+
72
+		return false, errNo
73
+	}
74
+
75
+	return true, 0
76
+}

+ 142
- 0
vendor/github.com/gofrs/flock/flock_windows.go View File

@@ -0,0 +1,142 @@
1
+// Copyright 2015 Tim Heckman. All rights reserved.
2
+// Use of this source code is governed by the BSD 3-Clause
3
+// license that can be found in the LICENSE file.
4
+
5
+package flock
6
+
7
+import (
8
+	"syscall"
9
+)
10
+
11
+// ErrorLockViolation is the error code returned from the Windows syscall when a
12
+// lock would block and you ask to fail immediately.
13
+const ErrorLockViolation syscall.Errno = 0x21 // 33
14
+
15
+// Lock is a blocking call to try and take an exclusive file lock. It will wait
16
+// until it is able to obtain the exclusive file lock. It's recommended that
17
+// TryLock() be used over this function. This function may block the ability to
18
+// query the current Locked() or RLocked() status due to a RW-mutex lock.
19
+//
20
+// If we are already locked, this function short-circuits and returns
21
+// immediately assuming it can take the mutex lock.
22
+func (f *Flock) Lock() error {
23
+	return f.lock(&f.l, winLockfileExclusiveLock)
24
+}
25
+
26
+// RLock is a blocking call to try and take a shared file lock. It will wait
27
+// until it is able to obtain the shared file lock. It's recommended that
28
+// TryRLock() be used over this function. This function may block the ability to
29
+// query the current Locked() or RLocked() status due to a RW-mutex lock.
30
+//
31
+// If we are already locked, this function short-circuits and returns
32
+// immediately assuming it can take the mutex lock.
33
+func (f *Flock) RLock() error {
34
+	return f.lock(&f.r, winLockfileSharedLock)
35
+}
36
+
37
+func (f *Flock) lock(locked *bool, flag uint32) error {
38
+	f.m.Lock()
39
+	defer f.m.Unlock()
40
+
41
+	if *locked {
42
+		return nil
43
+	}
44
+
45
+	if f.fh == nil {
46
+		if err := f.setFh(); err != nil {
47
+			return err
48
+		}
49
+		defer f.ensureFhState()
50
+	}
51
+
52
+	if _, errNo := lockFileEx(syscall.Handle(f.fh.Fd()), flag, 0, 1, 0, &syscall.Overlapped{}); errNo > 0 {
53
+		return errNo
54
+	}
55
+
56
+	*locked = true
57
+	return nil
58
+}
59
+
60
+// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so
61
+// while it is running the Locked() and RLocked() functions will be blocked.
62
+//
63
+// This function short-circuits if we are unlocked already. If not, it calls
64
+// UnlockFileEx() on the file and closes the file descriptor. It does not remove
65
+// the file from disk. It's up to your application to do.
66
+func (f *Flock) Unlock() error {
67
+	f.m.Lock()
68
+	defer f.m.Unlock()
69
+
70
+	// if we aren't locked or if the lockfile instance is nil
71
+	// just return a nil error because we are unlocked
72
+	if (!f.l && !f.r) || f.fh == nil {
73
+		return nil
74
+	}
75
+
76
+	// mark the file as unlocked
77
+	if _, errNo := unlockFileEx(syscall.Handle(f.fh.Fd()), 0, 1, 0, &syscall.Overlapped{}); errNo > 0 {
78
+		return errNo
79
+	}
80
+
81
+	f.fh.Close()
82
+
83
+	f.l = false
84
+	f.r = false
85
+	f.fh = nil
86
+
87
+	return nil
88
+}
89
+
90
+// TryLock is the preferred function for taking an exclusive file lock. This
91
+// function does take a RW-mutex lock before it tries to lock the file, so there
92
+// is the possibility that this function may block for a short time if another
93
+// goroutine is trying to take any action.
94
+//
95
+// The actual file lock is non-blocking. If we are unable to get the exclusive
96
+// file lock, the function will return false instead of waiting for the lock. If
97
+// we get the lock, we also set the *Flock instance as being exclusive-locked.
98
+func (f *Flock) TryLock() (bool, error) {
99
+	return f.try(&f.l, winLockfileExclusiveLock)
100
+}
101
+
102
+// TryRLock is the preferred function for taking a shared file lock. This
103
+// function does take a RW-mutex lock before it tries to lock the file, so there
104
+// is the possibility that this function may block for a short time if another
105
+// goroutine is trying to take any action.
106
+//
107
+// The actual file lock is non-blocking. If we are unable to get the shared file
108
+// lock, the function will return false instead of waiting for the lock. If we
109
+// get the lock, we also set the *Flock instance as being shared-locked.
110
+func (f *Flock) TryRLock() (bool, error) {
111
+	return f.try(&f.r, winLockfileSharedLock)
112
+}
113
+
114
+func (f *Flock) try(locked *bool, flag uint32) (bool, error) {
115
+	f.m.Lock()
116
+	defer f.m.Unlock()
117
+
118
+	if *locked {
119
+		return true, nil
120
+	}
121
+
122
+	if f.fh == nil {
123
+		if err := f.setFh(); err != nil {
124
+			return false, err
125
+		}
126
+		defer f.ensureFhState()
127
+	}
128
+
129
+	_, errNo := lockFileEx(syscall.Handle(f.fh.Fd()), flag|winLockfileFailImmediately, 0, 1, 0, &syscall.Overlapped{})
130
+
131
+	if errNo > 0 {
132
+		if errNo == ErrorLockViolation || errNo == syscall.ERROR_IO_PENDING {
133
+			return false, nil
134
+		}
135
+
136
+		return false, errNo
137
+	}
138
+
139
+	*locked = true
140
+
141
+	return true, nil
142
+}

+ 3
- 0
vendor/modules.txt View File

@@ -27,6 +27,9 @@ github.com/ergochat/irc-go/ircutils
27 27
 github.com/go-sql-driver/mysql
28 28
 # github.com/go-test/deep v1.0.6
29 29
 ## explicit; go 1.13
30
+# github.com/gofrs/flock v0.8.1
31
+## explicit
32
+github.com/gofrs/flock
30 33
 # github.com/golang-jwt/jwt v3.2.2+incompatible
31 34
 ## explicit
32 35
 github.com/golang-jwt/jwt

Loading…
Cancel
Save