2016-11-22 19:32:10 +00:00
|
|
|
package storage
|
|
|
|
|
|
|
|
import (
|
2017-07-20 20:31:51 +00:00
|
|
|
"fmt"
|
2016-11-22 19:32:10 +00:00
|
|
|
"path/filepath"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2017-07-20 20:31:51 +00:00
|
|
|
"github.com/pkg/errors"
|
2016-11-22 19:32:10 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// A Locker represents a file lock where the file is used to cache an
|
|
|
|
// identifier of the last party that made changes to whatever's being protected
|
|
|
|
// by the lock.
|
|
|
|
type Locker interface {
|
|
|
|
sync.Locker
|
|
|
|
|
|
|
|
// Touch records, for others sharing the lock, that the caller was the
|
|
|
|
// last writer. It should only be called with the lock held.
|
|
|
|
Touch() error
|
|
|
|
|
|
|
|
// Modified() checks if the most recent writer was a party other than the
|
|
|
|
// last recorded writer. It should only be called with the lock held.
|
|
|
|
Modified() (bool, error)
|
|
|
|
|
|
|
|
// TouchedSince() checks if the most recent writer modified the file (likely using Touch()) after the specified time.
|
|
|
|
TouchedSince(when time.Time) bool
|
2017-07-20 20:31:51 +00:00
|
|
|
|
|
|
|
// IsReadWrite() checks if the lock file is read-write
|
|
|
|
IsReadWrite() bool
|
2016-11-22 19:32:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
2018-03-01 21:40:22 +00:00
|
|
|
lockfiles map[string]Locker
|
2016-11-22 19:32:10 +00:00
|
|
|
lockfilesLock sync.Mutex
|
|
|
|
)
|
|
|
|
|
2017-07-20 20:31:51 +00:00
|
|
|
// GetLockfile opens a read-write lock file, creating it if necessary. The
|
|
|
|
// Locker object it returns will be returned unlocked.
|
2016-11-22 19:32:10 +00:00
|
|
|
func GetLockfile(path string) (Locker, error) {
|
|
|
|
lockfilesLock.Lock()
|
|
|
|
defer lockfilesLock.Unlock()
|
|
|
|
if lockfiles == nil {
|
2018-03-01 21:40:22 +00:00
|
|
|
lockfiles = make(map[string]Locker)
|
2016-11-22 19:32:10 +00:00
|
|
|
}
|
2017-07-20 20:31:51 +00:00
|
|
|
cleanPath := filepath.Clean(path)
|
|
|
|
if locker, ok := lockfiles[cleanPath]; ok {
|
|
|
|
if !locker.IsReadWrite() {
|
|
|
|
return nil, errors.Wrapf(ErrLockReadOnly, "lock %q is a read-only lock", cleanPath)
|
|
|
|
}
|
2016-11-22 19:32:10 +00:00
|
|
|
return locker, nil
|
|
|
|
}
|
2018-03-01 21:40:22 +00:00
|
|
|
locker, err := getLockFile(path, false) // platform dependent locker
|
2016-11-22 19:32:10 +00:00
|
|
|
if err != nil {
|
2018-03-01 21:40:22 +00:00
|
|
|
return nil, err
|
2016-11-22 19:32:10 +00:00
|
|
|
}
|
|
|
|
lockfiles[filepath.Clean(path)] = locker
|
|
|
|
return locker, nil
|
|
|
|
}
|
|
|
|
|
2017-07-20 20:31:51 +00:00
|
|
|
// GetROLockfile opens a read-only lock file. The Locker object it returns
|
|
|
|
// will be returned unlocked.
|
|
|
|
func GetROLockfile(path string) (Locker, error) {
|
|
|
|
lockfilesLock.Lock()
|
|
|
|
defer lockfilesLock.Unlock()
|
|
|
|
if lockfiles == nil {
|
2018-03-01 21:40:22 +00:00
|
|
|
lockfiles = make(map[string]Locker)
|
2017-07-20 20:31:51 +00:00
|
|
|
}
|
|
|
|
cleanPath := filepath.Clean(path)
|
|
|
|
if locker, ok := lockfiles[cleanPath]; ok {
|
|
|
|
if locker.IsReadWrite() {
|
|
|
|
return nil, fmt.Errorf("lock %q is a read-write lock", cleanPath)
|
|
|
|
}
|
|
|
|
return locker, nil
|
|
|
|
}
|
2018-03-01 21:40:22 +00:00
|
|
|
locker, err := getLockFile(path, true) // platform dependent locker
|
2017-07-20 20:31:51 +00:00
|
|
|
if err != nil {
|
2018-03-01 21:40:22 +00:00
|
|
|
return nil, err
|
2017-07-20 20:31:51 +00:00
|
|
|
}
|
|
|
|
lockfiles[filepath.Clean(path)] = locker
|
|
|
|
return locker, nil
|
|
|
|
}
|