aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/rogpeppe/go-internal/lockedfile
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2024-11-11 11:41:38 +0100
committerTaras Madan <tarasmadan@google.com>2024-11-11 11:10:48 +0000
commit27e76fae2ee2d84dc7db63af1d9ed7358ba35b7a (patch)
treeed19c0e35e272b3c4cc5a2f2c595e035b2428337 /vendor/github.com/rogpeppe/go-internal/lockedfile
parent621e84e063b0e15b23e17780338627c509e1b9e8 (diff)
vendor: update
Diffstat (limited to 'vendor/github.com/rogpeppe/go-internal/lockedfile')
-rw-r--r--vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock.go99
-rw-r--r--vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock_fcntl.go214
-rw-r--r--vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock_other.go36
-rw-r--r--vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock_unix.go44
-rw-r--r--vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock_windows.go67
-rw-r--r--vendor/github.com/rogpeppe/go-internal/lockedfile/lockedfile.go187
-rw-r--r--vendor/github.com/rogpeppe/go-internal/lockedfile/lockedfile_filelock.go65
-rw-r--r--vendor/github.com/rogpeppe/go-internal/lockedfile/lockedfile_plan9.go94
-rw-r--r--vendor/github.com/rogpeppe/go-internal/lockedfile/mutex.go67
9 files changed, 873 insertions, 0 deletions
diff --git a/vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock.go b/vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock.go
new file mode 100644
index 000000000..05f27c321
--- /dev/null
+++ b/vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock.go
@@ -0,0 +1,99 @@
+// 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.
+
+// Package filelock provides a platform-independent API for advisory file
+// locking. Calls to functions in this package on platforms that do not support
+// advisory locks will return errors for which IsNotSupported returns true.
+package filelock
+
+import (
+ "errors"
+ "io/fs"
+ "os"
+)
+
+// A File provides the minimal set of methods required to lock an open file.
+// File implementations must be usable as map keys.
+// The usual implementation is *os.File.
+type File interface {
+ // Name returns the name of the file.
+ Name() string
+
+ // Fd returns a valid file descriptor.
+ // (If the File is an *os.File, it must not be closed.)
+ Fd() uintptr
+
+ // Stat returns the FileInfo structure describing file.
+ Stat() (fs.FileInfo, error)
+}
+
+// Lock places an advisory write lock on the file, blocking until it can be
+// locked.
+//
+// If Lock returns nil, no other process will be able to place a read or write
+// lock on the file until this process exits, closes f, or calls Unlock on it.
+//
+// If f's descriptor is already read- or write-locked, the behavior of Lock is
+// unspecified.
+//
+// Closing the file may or may not release the lock promptly. Callers should
+// ensure that Unlock is always called when Lock succeeds.
+func Lock(f File) error {
+ return lock(f, writeLock)
+}
+
+// RLock places an advisory read lock on the file, blocking until it can be locked.
+//
+// If RLock returns nil, no other process will be able to place a write lock on
+// the file until this process exits, closes f, or calls Unlock on it.
+//
+// If f is already read- or write-locked, the behavior of RLock is unspecified.
+//
+// Closing the file may or may not release the lock promptly. Callers should
+// ensure that Unlock is always called if RLock succeeds.
+func RLock(f File) error {
+ return lock(f, readLock)
+}
+
+// Unlock removes an advisory lock placed on f by this process.
+//
+// The caller must not attempt to unlock a file that is not locked.
+func Unlock(f File) error {
+ return unlock(f)
+}
+
+// String returns the name of the function corresponding to lt
+// (Lock, RLock, or Unlock).
+func (lt lockType) String() string {
+ switch lt {
+ case readLock:
+ return "RLock"
+ case writeLock:
+ return "Lock"
+ default:
+ return "Unlock"
+ }
+}
+
+// IsNotSupported returns a boolean indicating whether the error is known to
+// report that a function is not supported (possibly for a specific input).
+// It is satisfied by ErrNotSupported as well as some syscall errors.
+func IsNotSupported(err error) bool {
+ return isNotSupported(underlyingError(err))
+}
+
+var ErrNotSupported = errors.New("operation not supported")
+
+// underlyingError returns the underlying error for known os error types.
+func underlyingError(err error) error {
+ switch err := err.(type) {
+ case *fs.PathError:
+ return err.Err
+ case *os.LinkError:
+ return err.Err
+ case *os.SyscallError:
+ return err.Err
+ }
+ return err
+}
diff --git a/vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock_fcntl.go b/vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock_fcntl.go
new file mode 100644
index 000000000..856804850
--- /dev/null
+++ b/vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock_fcntl.go
@@ -0,0 +1,214 @@
+// 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.
+
+//go:build aix || (solaris && !illumos)
+
+// 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.
+//
+// Most platforms provide some alternative API, such as an 'flock' system call
+// or an F_OFD_SETLK command for 'fcntl', that allows for better concurrency and
+// does not require per-inode bookkeeping in the application.
+
+package filelock
+
+import (
+ "errors"
+ "io"
+ "io/fs"
+ "math/rand"
+ "sync"
+ "syscall"
+ "time"
+)
+
+type lockType int16
+
+const (
+ readLock lockType = syscall.F_RDLCK
+ writeLock lockType = syscall.F_WRLCK
+)
+
+type inode = uint64 // type of syscall.Stat_t.Ino
+
+type inodeLock struct {
+ owner File
+ queue []<-chan File
+}
+
+var (
+ mu sync.Mutex
+ inodes = map[File]inode{}
+ locks = map[inode]inodeLock{}
+)
+
+func lock(f File, lt lockType) (err 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.Stat()
+ if err != nil {
+ return err
+ }
+ ino := fi.Sys().(*syscall.Stat_t).Ino
+
+ mu.Lock()
+ if i, dup := inodes[f]; dup && i != ino {
+ mu.Unlock()
+ return &fs.PathError{
+ Op: lt.String(),
+ Path: f.Name(),
+ Err: errors.New("inode for file changed since last Lock or RLock"),
+ }
+ }
+ inodes[f] = ino
+
+ var wait chan File
+ 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 {
+ // Already owned: add a channel to wait on.
+ wait = make(chan File)
+ l.queue = append(l.queue, wait)
+ }
+ locks[ino] = l
+ mu.Unlock()
+
+ if wait != nil {
+ wait <- f
+ }
+
+ // Spurious EDEADLK errors arise on platforms that compute deadlock graphs at
+ // the process, rather than thread, level. Consider processes P and Q, with
+ // threads P.1, P.2, and Q.3. The following trace is NOT a deadlock, but will be
+ // reported as a deadlock on systems that consider only process granularity:
+ //
+ // P.1 locks file A.
+ // Q.3 locks file B.
+ // Q.3 blocks on file A.
+ // P.2 blocks on file B. (This is erroneously reported as a deadlock.)
+ // P.1 unlocks file A.
+ // Q.3 unblocks and locks file A.
+ // Q.3 unlocks files A and B.
+ // P.2 unblocks and locks file B.
+ // P.2 unlocks file B.
+ //
+ // These spurious errors were observed in practice on AIX and Solaris in
+ // cmd/go: see https://golang.org/issue/32817.
+ //
+ // We work around this bug by treating EDEADLK as always spurious. If there
+ // really is a lock-ordering bug between the interacting processes, it will
+ // become a livelock instead, but that's not appreciably worse than if we had
+ // a proper flock implementation (which generally does not even attempt to
+ // diagnose deadlocks).
+ //
+ // In the above example, that changes the trace to:
+ //
+ // P.1 locks file A.
+ // Q.3 locks file B.
+ // Q.3 blocks on file A.
+ // P.2 spuriously fails to lock file B and goes to sleep.
+ // P.1 unlocks file A.
+ // Q.3 unblocks and locks file A.
+ // Q.3 unlocks files A and B.
+ // P.2 wakes up and locks file B.
+ // P.2 unlocks file B.
+ //
+ // We know that the retry loop will not introduce a *spurious* livelock
+ // because, according to the POSIX specification, EDEADLK is only to be
+ // returned when “the lock is blocked by a lock from another process”.
+ // If that process is blocked on some lock that we are holding, then the
+ // resulting livelock is due to a real deadlock (and would manifest as such
+ // when using, for example, the flock implementation of this package).
+ // If the other process is *not* blocked on some other lock that we are
+ // holding, then it will eventually release the requested lock.
+
+ nextSleep := 1 * time.Millisecond
+ const maxSleep = 500 * time.Millisecond
+ for {
+ err = setlkw(f.Fd(), lt)
+ if err != syscall.EDEADLK {
+ break
+ }
+ time.Sleep(nextSleep)
+
+ nextSleep += nextSleep
+ if nextSleep > maxSleep {
+ nextSleep = maxSleep
+ }
+ // Apply 10% jitter to avoid synchronizing collisions when we finally unblock.
+ nextSleep += time.Duration((0.1*rand.Float64() - 0.05) * float64(nextSleep))
+ }
+
+ if err != nil {
+ unlock(f)
+ return &fs.PathError{
+ Op: lt.String(),
+ Path: f.Name(),
+ Err: err,
+ }
+ }
+
+ return nil
+}
+
+func unlock(f File) error {
+ var owner File
+
+ mu.Lock()
+ ino, ok := inodes[f]
+ if ok {
+ owner = locks[ino].owner
+ }
+ mu.Unlock()
+
+ if owner != f {
+ panic("unlock called on a file that is not locked")
+ }
+
+ err := setlkw(f.Fd(), syscall.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
+}
+
+// setlkw calls FcntlFlock with F_SETLKW for the entire file indicated by fd.
+func setlkw(fd uintptr, lt lockType) error {
+ for {
+ err := syscall.FcntlFlock(fd, syscall.F_SETLKW, &syscall.Flock_t{
+ Type: int16(lt),
+ Whence: io.SeekStart,
+ Start: 0,
+ Len: 0, // All bytes.
+ })
+ if err != syscall.EINTR {
+ return err
+ }
+ }
+}
+
+func isNotSupported(err error) bool {
+ return err == syscall.ENOSYS || err == syscall.ENOTSUP || err == syscall.EOPNOTSUPP || err == ErrNotSupported
+}
diff --git a/vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock_other.go b/vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock_other.go
new file mode 100644
index 000000000..7bdd62bd9
--- /dev/null
+++ b/vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock_other.go
@@ -0,0 +1,36 @@
+// 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.
+
+//go:build !unix && !windows
+
+package filelock
+
+import "io/fs"
+
+type lockType int8
+
+const (
+ readLock = iota + 1
+ writeLock
+)
+
+func lock(f File, lt lockType) error {
+ return &fs.PathError{
+ Op: lt.String(),
+ Path: f.Name(),
+ Err: ErrNotSupported,
+ }
+}
+
+func unlock(f File) error {
+ return &fs.PathError{
+ Op: "Unlock",
+ Path: f.Name(),
+ Err: ErrNotSupported,
+ }
+}
+
+func isNotSupported(err error) bool {
+ return err == ErrNotSupported
+}
diff --git a/vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock_unix.go b/vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock_unix.go
new file mode 100644
index 000000000..d7778d05d
--- /dev/null
+++ b/vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock_unix.go
@@ -0,0 +1,44 @@
+// 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.
+
+//go:build darwin || dragonfly || freebsd || illumos || linux || netbsd || openbsd
+
+package filelock
+
+import (
+ "io/fs"
+ "syscall"
+)
+
+type lockType int16
+
+const (
+ readLock lockType = syscall.LOCK_SH
+ writeLock lockType = syscall.LOCK_EX
+)
+
+func lock(f File, lt lockType) (err error) {
+ for {
+ err = syscall.Flock(int(f.Fd()), int(lt))
+ if err != syscall.EINTR {
+ break
+ }
+ }
+ if err != nil {
+ return &fs.PathError{
+ Op: lt.String(),
+ Path: f.Name(),
+ Err: err,
+ }
+ }
+ return nil
+}
+
+func unlock(f File) error {
+ return lock(f, syscall.LOCK_UN)
+}
+
+func isNotSupported(err error) bool {
+ return err == syscall.ENOSYS || err == syscall.ENOTSUP || err == syscall.EOPNOTSUPP || err == ErrNotSupported
+}
diff --git a/vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock_windows.go b/vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock_windows.go
new file mode 100644
index 000000000..ceab65b02
--- /dev/null
+++ b/vendor/github.com/rogpeppe/go-internal/lockedfile/internal/filelock/filelock_windows.go
@@ -0,0 +1,67 @@
+// 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.
+
+//go:build windows
+
+package filelock
+
+import (
+ "io/fs"
+ "syscall"
+
+ "github.com/rogpeppe/go-internal/internal/syscall/windows"
+)
+
+type lockType uint32
+
+const (
+ readLock lockType = 0
+ writeLock lockType = windows.LOCKFILE_EXCLUSIVE_LOCK
+)
+
+const (
+ reserved = 0
+ allBytes = ^uint32(0)
+)
+
+func lock(f File, lt lockType) error {
+ // Per https://golang.org/issue/19098, “Programs currently expect the Fd
+ // method to return a handle that uses ordinary synchronous I/O.”
+ // However, LockFileEx still requires an OVERLAPPED structure,
+ // which contains the file offset of the beginning of the lock range.
+ // We want to lock the entire file, so we leave the offset as zero.
+ ol := new(syscall.Overlapped)
+
+ err := windows.LockFileEx(syscall.Handle(f.Fd()), uint32(lt), reserved, allBytes, allBytes, ol)
+ if err != nil {
+ return &fs.PathError{
+ Op: lt.String(),
+ Path: f.Name(),
+ Err: err,
+ }
+ }
+ return nil
+}
+
+func unlock(f File) error {
+ ol := new(syscall.Overlapped)
+ err := windows.UnlockFileEx(syscall.Handle(f.Fd()), reserved, allBytes, allBytes, ol)
+ if err != nil {
+ return &fs.PathError{
+ Op: "Unlock",
+ Path: f.Name(),
+ Err: err,
+ }
+ }
+ return nil
+}
+
+func isNotSupported(err error) bool {
+ switch err {
+ case windows.ERROR_NOT_SUPPORTED, windows.ERROR_CALL_NOT_IMPLEMENTED, ErrNotSupported:
+ return true
+ default:
+ return false
+ }
+}
diff --git a/vendor/github.com/rogpeppe/go-internal/lockedfile/lockedfile.go b/vendor/github.com/rogpeppe/go-internal/lockedfile/lockedfile.go
new file mode 100644
index 000000000..82e1a8967
--- /dev/null
+++ b/vendor/github.com/rogpeppe/go-internal/lockedfile/lockedfile.go
@@ -0,0 +1,187 @@
+// 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.
+
+// Package lockedfile creates and manipulates files whose contents should only
+// change atomically.
+package lockedfile
+
+import (
+ "fmt"
+ "io"
+ "io/fs"
+ "os"
+ "runtime"
+)
+
+// A File is a locked *os.File.
+//
+// Closing the file releases the lock.
+//
+// If the program exits while a file is locked, the operating system releases
+// the lock but may not do so promptly: callers must ensure that all locked
+// files are closed before exiting.
+type File struct {
+ osFile
+ closed bool
+}
+
+// osFile embeds a *os.File while keeping the pointer itself unexported.
+// (When we close a File, it must be the same file descriptor that we opened!)
+type osFile struct {
+ *os.File
+}
+
+// OpenFile is like os.OpenFile, but returns a locked file.
+// If flag includes os.O_WRONLY or os.O_RDWR, the file is write-locked;
+// otherwise, it is read-locked.
+func OpenFile(name string, flag int, perm fs.FileMode) (*File, error) {
+ var (
+ f = new(File)
+ err error
+ )
+ f.osFile.File, err = openFile(name, flag, perm)
+ if err != nil {
+ return nil, err
+ }
+
+ // Although the operating system will drop locks for open files when the go
+ // command exits, we want to hold locks for as little time as possible, and we
+ // especially don't want to leave a file locked after we're done with it. Our
+ // Close method is what releases the locks, so use a finalizer to report
+ // missing Close calls on a best-effort basis.
+ runtime.SetFinalizer(f, func(f *File) {
+ panic(fmt.Sprintf("lockedfile.File %s became unreachable without a call to Close", f.Name()))
+ })
+
+ return f, nil
+}
+
+// Open is like os.Open, but returns a read-locked file.
+func Open(name string) (*File, error) {
+ return OpenFile(name, os.O_RDONLY, 0)
+}
+
+// Create is like os.Create, but returns a write-locked file.
+func Create(name string) (*File, error) {
+ return OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+}
+
+// Edit creates the named file with mode 0666 (before umask),
+// but does not truncate existing contents.
+//
+// If Edit succeeds, methods on the returned File can be used for I/O.
+// The associated file descriptor has mode O_RDWR and the file is write-locked.
+func Edit(name string) (*File, error) {
+ return OpenFile(name, os.O_RDWR|os.O_CREATE, 0666)
+}
+
+// Close unlocks and closes the underlying file.
+//
+// Close may be called multiple times; all calls after the first will return a
+// non-nil error.
+func (f *File) Close() error {
+ if f.closed {
+ return &fs.PathError{
+ Op: "close",
+ Path: f.Name(),
+ Err: fs.ErrClosed,
+ }
+ }
+ f.closed = true
+
+ err := closeFile(f.osFile.File)
+ runtime.SetFinalizer(f, nil)
+ return err
+}
+
+// Read opens the named file with a read-lock and returns its contents.
+func Read(name string) ([]byte, error) {
+ f, err := Open(name)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ return io.ReadAll(f)
+}
+
+// Write opens the named file (creating it with the given permissions if needed),
+// then write-locks it and overwrites it with the given content.
+func Write(name string, content io.Reader, perm fs.FileMode) (err error) {
+ f, err := OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
+ if err != nil {
+ return err
+ }
+
+ _, err = io.Copy(f, content)
+ if closeErr := f.Close(); err == nil {
+ err = closeErr
+ }
+ return err
+}
+
+// Transform invokes t with the result of reading the named file, with its lock
+// still held.
+//
+// If t returns a nil error, Transform then writes the returned contents back to
+// the file, making a best effort to preserve existing contents on error.
+//
+// t must not modify the slice passed to it.
+func Transform(name string, t func([]byte) ([]byte, error)) (err error) {
+ f, err := Edit(name)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ old, err := io.ReadAll(f)
+ if err != nil {
+ return err
+ }
+
+ new, err := t(old)
+ if err != nil {
+ return err
+ }
+
+ if len(new) > len(old) {
+ // The overall file size is increasing, so write the tail first: if we're
+ // about to run out of space on the disk, we would rather detect that
+ // failure before we have overwritten the original contents.
+ if _, err := f.WriteAt(new[len(old):], int64(len(old))); err != nil {
+ // Make a best effort to remove the incomplete tail.
+ f.Truncate(int64(len(old)))
+ return err
+ }
+ }
+
+ // We're about to overwrite the old contents. In case of failure, make a best
+ // effort to roll back before we close the file.
+ defer func() {
+ if err != nil {
+ if _, err := f.WriteAt(old, 0); err == nil {
+ f.Truncate(int64(len(old)))
+ }
+ }
+ }()
+
+ if len(new) >= len(old) {
+ if _, err := f.WriteAt(new[:len(old)], 0); err != nil {
+ return err
+ }
+ } else {
+ if _, err := f.WriteAt(new, 0); err != nil {
+ return err
+ }
+ // The overall file size is decreasing, so shrink the file to its final size
+ // after writing. We do this after writing (instead of before) so that if
+ // the write fails, enough filesystem space will likely still be reserved
+ // to contain the previous contents.
+ if err := f.Truncate(int64(len(new))); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/rogpeppe/go-internal/lockedfile/lockedfile_filelock.go b/vendor/github.com/rogpeppe/go-internal/lockedfile/lockedfile_filelock.go
new file mode 100644
index 000000000..454c3a42c
--- /dev/null
+++ b/vendor/github.com/rogpeppe/go-internal/lockedfile/lockedfile_filelock.go
@@ -0,0 +1,65 @@
+// 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.
+
+//go:build !plan9
+
+package lockedfile
+
+import (
+ "io/fs"
+ "os"
+
+ "github.com/rogpeppe/go-internal/lockedfile/internal/filelock"
+)
+
+func openFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
+ // On BSD systems, we could add the O_SHLOCK or O_EXLOCK flag to the OpenFile
+ // call instead of locking separately, but we have to support separate locking
+ // calls for Linux and Windows anyway, so it's simpler to use that approach
+ // consistently.
+
+ f, err := os.OpenFile(name, flag&^os.O_TRUNC, perm)
+ if err != nil {
+ return nil, err
+ }
+
+ switch flag & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR) {
+ case os.O_WRONLY, os.O_RDWR:
+ err = filelock.Lock(f)
+ default:
+ err = filelock.RLock(f)
+ }
+ if err != nil {
+ f.Close()
+ return nil, err
+ }
+
+ if flag&os.O_TRUNC == os.O_TRUNC {
+ if err := f.Truncate(0); err != nil {
+ // The documentation for os.O_TRUNC says “if possible, truncate file when
+ // opened”, but doesn't define “possible” (golang.org/issue/28699).
+ // We'll treat regular files (and symlinks to regular files) as “possible”
+ // and ignore errors for the rest.
+ if fi, statErr := f.Stat(); statErr != nil || fi.Mode().IsRegular() {
+ filelock.Unlock(f)
+ f.Close()
+ return nil, err
+ }
+ }
+ }
+
+ return f, nil
+}
+
+func closeFile(f *os.File) error {
+ // Since locking syscalls operate on file descriptors, we must unlock the file
+ // while the descriptor is still valid — that is, before the file is closed —
+ // and avoid unlocking files that are already closed.
+ err := filelock.Unlock(f)
+
+ if closeErr := f.Close(); err == nil {
+ err = closeErr
+ }
+ return err
+}
diff --git a/vendor/github.com/rogpeppe/go-internal/lockedfile/lockedfile_plan9.go b/vendor/github.com/rogpeppe/go-internal/lockedfile/lockedfile_plan9.go
new file mode 100644
index 000000000..a2ce794b9
--- /dev/null
+++ b/vendor/github.com/rogpeppe/go-internal/lockedfile/lockedfile_plan9.go
@@ -0,0 +1,94 @@
+// 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.
+
+//go:build plan9
+
+package lockedfile
+
+import (
+ "io/fs"
+ "math/rand"
+ "os"
+ "strings"
+ "time"
+)
+
+// Opening an exclusive-use file returns an error.
+// The expected error strings are:
+//
+// - "open/create -- file is locked" (cwfs, kfs)
+// - "exclusive lock" (fossil)
+// - "exclusive use file already open" (ramfs)
+var lockedErrStrings = [...]string{
+ "file is locked",
+ "exclusive lock",
+ "exclusive use file already open",
+}
+
+// Even though plan9 doesn't support the Lock/RLock/Unlock functions to
+// manipulate already-open files, IsLocked is still meaningful: os.OpenFile
+// itself may return errors that indicate that a file with the ModeExclusive bit
+// set is already open.
+func isLocked(err error) bool {
+ s := err.Error()
+
+ for _, frag := range lockedErrStrings {
+ if strings.Contains(s, frag) {
+ return true
+ }
+ }
+
+ return false
+}
+
+func openFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
+ // Plan 9 uses a mode bit instead of explicit lock/unlock syscalls.
+ //
+ // Per http://man.cat-v.org/plan_9/5/stat: “Exclusive use files may be open
+ // for I/O by only one fid at a time across all clients of the server. If a
+ // second open is attempted, it draws an error.”
+ //
+ // So we can try to open a locked file, but if it fails we're on our own to
+ // figure out when it becomes available. We'll use exponential backoff with
+ // some jitter and an arbitrary limit of 500ms.
+
+ // If the file was unpacked or created by some other program, it might not
+ // have the ModeExclusive bit set. Set it before we call OpenFile, so that we
+ // can be confident that a successful OpenFile implies exclusive use.
+ if fi, err := os.Stat(name); err == nil {
+ if fi.Mode()&fs.ModeExclusive == 0 {
+ if err := os.Chmod(name, fi.Mode()|fs.ModeExclusive); err != nil {
+ return nil, err
+ }
+ }
+ } else if !os.IsNotExist(err) {
+ return nil, err
+ }
+
+ nextSleep := 1 * time.Millisecond
+ const maxSleep = 500 * time.Millisecond
+ for {
+ f, err := os.OpenFile(name, flag, perm|fs.ModeExclusive)
+ if err == nil {
+ return f, nil
+ }
+
+ if !isLocked(err) {
+ return nil, err
+ }
+
+ time.Sleep(nextSleep)
+
+ nextSleep += nextSleep
+ if nextSleep > maxSleep {
+ nextSleep = maxSleep
+ }
+ // Apply 10% jitter to avoid synchronizing collisions.
+ nextSleep += time.Duration((0.1*rand.Float64() - 0.05) * float64(nextSleep))
+ }
+}
+
+func closeFile(f *os.File) error {
+ return f.Close()
+}
diff --git a/vendor/github.com/rogpeppe/go-internal/lockedfile/mutex.go b/vendor/github.com/rogpeppe/go-internal/lockedfile/mutex.go
new file mode 100644
index 000000000..180a36c62
--- /dev/null
+++ b/vendor/github.com/rogpeppe/go-internal/lockedfile/mutex.go
@@ -0,0 +1,67 @@
+// 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.
+
+package lockedfile
+
+import (
+ "fmt"
+ "os"
+ "sync"
+)
+
+// A Mutex provides mutual exclusion within and across processes by locking a
+// well-known file. Such a file generally guards some other part of the
+// filesystem: for example, a Mutex file in a directory might guard access to
+// the entire tree rooted in that directory.
+//
+// Mutex does not implement sync.Locker: unlike a sync.Mutex, a lockedfile.Mutex
+// can fail to lock (e.g. if there is a permission error in the filesystem).
+//
+// Like a sync.Mutex, a Mutex may be included as a field of a larger struct but
+// must not be copied after first use. The Path field must be set before first
+// use and must not be change thereafter.
+type Mutex struct {
+ Path string // The path to the well-known lock file. Must be non-empty.
+ mu sync.Mutex // A redundant mutex. The race detector doesn't know about file locking, so in tests we may need to lock something that it understands.
+}
+
+// MutexAt returns a new Mutex with Path set to the given non-empty path.
+func MutexAt(path string) *Mutex {
+ if path == "" {
+ panic("lockedfile.MutexAt: path must be non-empty")
+ }
+ return &Mutex{Path: path}
+}
+
+func (mu *Mutex) String() string {
+ return fmt.Sprintf("lockedfile.Mutex(%s)", mu.Path)
+}
+
+// Lock attempts to lock the Mutex.
+//
+// If successful, Lock returns a non-nil unlock function: it is provided as a
+// return-value instead of a separate method to remind the caller to check the
+// accompanying error. (See https://golang.org/issue/20803.)
+func (mu *Mutex) Lock() (unlock func(), err error) {
+ if mu.Path == "" {
+ panic("lockedfile.Mutex: missing Path during Lock")
+ }
+
+ // We could use either O_RDWR or O_WRONLY here. If we choose O_RDWR and the
+ // file at mu.Path is write-only, the call to OpenFile will fail with a
+ // permission error. That's actually what we want: if we add an RLock method
+ // in the future, it should call OpenFile with O_RDONLY and will require the
+ // files must be readable, so we should not let the caller make any
+ // assumptions about Mutex working with write-only files.
+ f, err := OpenFile(mu.Path, os.O_RDWR|os.O_CREATE, 0666)
+ if err != nil {
+ return nil, err
+ }
+ mu.mu.Lock()
+
+ return func() {
+ mu.mu.Unlock()
+ f.Close()
+ }, nil
+}