mirror of
https://github.com/octoleo/restic.git
synced 2024-12-27 12:42:49 +00:00
cmd/restic: Add command unlock
, improve error message
This commit is contained in:
parent
0ad3d71f01
commit
e657287eac
43
cmd/restic/cmd_unlock.go
Normal file
43
cmd/restic/cmd_unlock.go
Normal file
@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import "github.com/restic/restic"
|
||||
|
||||
type CmdUnlock struct {
|
||||
RemoveAll bool `long:"remove-all" description:"Remove all locks, even stale ones"`
|
||||
|
||||
global *GlobalOptions
|
||||
}
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("unlock",
|
||||
"remove locks",
|
||||
"The unlock command checks for stale locks and removes them",
|
||||
&CmdUnlock{global: &globalOpts})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd CmdUnlock) Usage() string {
|
||||
return "[unlock-options]"
|
||||
}
|
||||
|
||||
func (cmd CmdUnlock) Execute(args []string) error {
|
||||
repo, err := cmd.global.OpenRepository()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fn := restic.RemoveStaleLocks
|
||||
if cmd.RemoveAll {
|
||||
fn = restic.RemoveAllLocks
|
||||
}
|
||||
|
||||
err = fn(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.global.Verbosef("successfully removed locks\n")
|
||||
return nil
|
||||
}
|
@ -14,19 +14,32 @@ import (
|
||||
var globalLocks []*restic.Lock
|
||||
|
||||
func lockRepo(repo *repository.Repository) (*restic.Lock, error) {
|
||||
lock, err := restic.NewLock(repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
globalLocks = append(globalLocks, lock)
|
||||
|
||||
return lock, err
|
||||
return lockRepository(repo, false)
|
||||
}
|
||||
|
||||
func lockRepoExclusive(repo *repository.Repository) (*restic.Lock, error) {
|
||||
lock, err := restic.NewExclusiveLock(repo)
|
||||
return lockRepository(repo, true)
|
||||
}
|
||||
|
||||
func lockRepository(repo *repository.Repository, exclusive bool) (*restic.Lock, error) {
|
||||
lockFn := restic.NewLock
|
||||
if exclusive {
|
||||
lockFn = restic.NewExclusiveLock
|
||||
}
|
||||
|
||||
lock, err := lockFn(repo)
|
||||
if err != nil {
|
||||
if restic.IsAlreadyLocked(err) {
|
||||
tpe := ""
|
||||
if exclusive {
|
||||
tpe = " exclusive"
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "unable to acquire%s lock for operation:\n", tpe)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
fmt.Fprintf(os.Stderr, "\nthe `unlock` command can be used to remove stale locks\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
49
lock.go
49
lock.go
@ -1,7 +1,7 @@
|
||||
package restic
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"os/user"
|
||||
@ -33,10 +33,24 @@ type Lock struct {
|
||||
lockID backend.ID
|
||||
}
|
||||
|
||||
var (
|
||||
ErrAlreadyLocked = errors.New("already locked")
|
||||
ErrStaleLockFound = errors.New("stale lock found")
|
||||
)
|
||||
// ErrAlreadyLocked is returned when NewLock or NewExclusiveLock are unable to
|
||||
// acquire the desired lock.
|
||||
type ErrAlreadyLocked struct {
|
||||
otherLock *Lock
|
||||
}
|
||||
|
||||
func (e ErrAlreadyLocked) Error() string {
|
||||
return fmt.Sprintf("repository is already locked by %v", e.otherLock)
|
||||
}
|
||||
|
||||
// IsAlreadyLocked returns true iff err is an instance of ErrAlreadyLocked.
|
||||
func IsAlreadyLocked(err error) bool {
|
||||
if _, ok := err.(ErrAlreadyLocked); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// NewLock returns a new, non-exclusive lock for the repository. If an
|
||||
// exclusive lock is already held by another process, ErrAlreadyLocked is
|
||||
@ -84,7 +98,7 @@ func newLock(repo *repository.Repository, excl bool) (*Lock, error) {
|
||||
|
||||
if err = lock.checkForOtherLocks(); err != nil {
|
||||
lock.Unlock()
|
||||
return nil, ErrAlreadyLocked
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return lock, nil
|
||||
@ -130,11 +144,11 @@ func (l *Lock) checkForOtherLocks() error {
|
||||
}
|
||||
|
||||
if l.Exclusive {
|
||||
return ErrAlreadyLocked
|
||||
return ErrAlreadyLocked{otherLock: lock}
|
||||
}
|
||||
|
||||
if !l.Exclusive && lock.Exclusive {
|
||||
return ErrAlreadyLocked
|
||||
return ErrAlreadyLocked{otherLock: lock}
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -206,6 +220,19 @@ func (l *Lock) Stale() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (l Lock) String() string {
|
||||
text := fmt.Sprintf("PID %d on %s by %s (UID %d, GID %d)\nlock was created at %s (%s ago)\nstorage ID %v",
|
||||
l.PID, l.Hostname, l.Username, l.UID, l.GID,
|
||||
l.Time.Format("2006-01-02 15:04:05"), time.Since(l.Time),
|
||||
l.lockID.Str())
|
||||
|
||||
if l.Stale() {
|
||||
text += " (stale)"
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
// listen for incoming SIGHUP and ignore
|
||||
var ignoreSIGHUP sync.Once
|
||||
|
||||
@ -247,3 +274,9 @@ func RemoveStaleLocks(repo *repository.Repository) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func RemoveAllLocks(repo *repository.Repository) error {
|
||||
return eachLock(repo, func(id backend.ID, lock *Lock, err error) error {
|
||||
return repo.Backend().Remove(backend.Lock, id.String())
|
||||
})
|
||||
}
|
||||
|
33
lock_test.go
33
lock_test.go
@ -67,8 +67,10 @@ func TestLockOnExclusiveLockedRepo(t *testing.T) {
|
||||
OK(t, err)
|
||||
|
||||
lock, err := restic.NewLock(repo)
|
||||
Assert(t, err == restic.ErrAlreadyLocked,
|
||||
Assert(t, err != nil,
|
||||
"create normal lock with exclusively locked repo didn't return an error")
|
||||
Assert(t, restic.IsAlreadyLocked(err),
|
||||
"create normal lock with exclusively locked repo didn't return the correct error")
|
||||
|
||||
OK(t, lock.Unlock())
|
||||
OK(t, elock.Unlock())
|
||||
@ -82,8 +84,10 @@ func TestExclusiveLockOnLockedRepo(t *testing.T) {
|
||||
OK(t, err)
|
||||
|
||||
lock, err := restic.NewExclusiveLock(repo)
|
||||
Assert(t, err == restic.ErrAlreadyLocked,
|
||||
"create exclusive lock with locked repo didn't return an error")
|
||||
Assert(t, err != nil,
|
||||
"create normal lock with exclusively locked repo didn't return an error")
|
||||
Assert(t, restic.IsAlreadyLocked(err),
|
||||
"create normal lock with exclusively locked repo didn't return the correct error")
|
||||
|
||||
OK(t, lock.Unlock())
|
||||
OK(t, elock.Unlock())
|
||||
@ -170,6 +174,29 @@ func TestLockWithStaleLock(t *testing.T) {
|
||||
OK(t, removeLock(repo, id2))
|
||||
}
|
||||
|
||||
func TestRemoveAllLocks(t *testing.T) {
|
||||
repo := SetupRepo()
|
||||
defer TeardownRepo(repo)
|
||||
|
||||
id1, err := createFakeLock(repo, time.Now().Add(-time.Hour), os.Getpid())
|
||||
OK(t, err)
|
||||
|
||||
id2, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid())
|
||||
OK(t, err)
|
||||
|
||||
id3, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid()+500)
|
||||
OK(t, err)
|
||||
|
||||
OK(t, restic.RemoveAllLocks(repo))
|
||||
|
||||
Assert(t, lockExists(repo, t, id1) == false,
|
||||
"lock still exists after RemoveAllLocks was called")
|
||||
Assert(t, lockExists(repo, t, id2) == false,
|
||||
"lock still exists after RemoveAllLocks was called")
|
||||
Assert(t, lockExists(repo, t, id3) == false,
|
||||
"lock still exists after RemoveAllLocks was called")
|
||||
}
|
||||
|
||||
func TestLockConflictingExclusiveLocks(t *testing.T) {
|
||||
repo := SetupRepo()
|
||||
defer TeardownRepo(repo)
|
||||
|
Loading…
Reference in New Issue
Block a user