* lib/model: Don't panic on failed chmod-back on directory (fixes #5836) This makes the "in writable dir"-wrapper log chmod-back errors instead of panicking. To do that we need a logger so the function moved into the model package which is also the only place it's used. The tests came along. (The test also exercised osutil.RenameOrCopy like some sort of piggybacking. I removed that.)
98 lines
2.4 KiB
Go
98 lines
2.4 KiB
Go
// Copyright (C) 2014 The Syncthing Authors.
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
|
package model
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/syncthing/syncthing/lib/fs"
|
|
)
|
|
|
|
type Holdable interface {
|
|
Holders() string
|
|
}
|
|
|
|
func newDeadlockDetector(timeout time.Duration) *deadlockDetector {
|
|
return &deadlockDetector{
|
|
timeout: timeout,
|
|
lockers: make(map[string]sync.Locker),
|
|
}
|
|
}
|
|
|
|
type deadlockDetector struct {
|
|
timeout time.Duration
|
|
lockers map[string]sync.Locker
|
|
}
|
|
|
|
func (d *deadlockDetector) Watch(name string, mut sync.Locker) {
|
|
d.lockers[name] = mut
|
|
go func() {
|
|
for {
|
|
time.Sleep(d.timeout / 4)
|
|
ok := make(chan bool, 2)
|
|
|
|
go func() {
|
|
mut.Lock()
|
|
_ = 1 // empty critical section
|
|
mut.Unlock()
|
|
ok <- true
|
|
}()
|
|
|
|
go func() {
|
|
time.Sleep(d.timeout)
|
|
ok <- false
|
|
}()
|
|
|
|
if r := <-ok; !r {
|
|
msg := fmt.Sprintf("deadlock detected at %s", name)
|
|
for otherName, otherMut := range d.lockers {
|
|
if otherHolder, ok := otherMut.(Holdable); ok {
|
|
msg += "\n===" + otherName + "===\n" + otherHolder.Holders()
|
|
}
|
|
}
|
|
panic(msg)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// inWritableDir calls fn(path), while making sure that the directory
|
|
// containing `path` is writable for the duration of the call.
|
|
func inWritableDir(fn func(string) error, targetFs fs.Filesystem, path string) error {
|
|
dir := filepath.Dir(path)
|
|
info, err := targetFs.Stat(dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !info.IsDir() {
|
|
return errors.New("Not a directory: " + path)
|
|
}
|
|
if info.Mode()&0200 == 0 {
|
|
// A non-writeable directory (for this user; we assume that's the
|
|
// relevant part). Temporarily change the mode so we can delete the
|
|
// file or directory inside it.
|
|
if err := targetFs.Chmod(dir, 0755); err == nil {
|
|
// Chmod succeeded, we should change the permissions back on the way
|
|
// out. If we fail we log the error as we have irrevocably messed up
|
|
// at this point. :( (The operation we were called to wrap has
|
|
// succeeded or failed on its own so returning an error to the
|
|
// caller is inappropriate.)
|
|
defer func() {
|
|
if err := targetFs.Chmod(dir, info.Mode()); err != nil && !fs.IsNotExist(err) {
|
|
l.Warnln("Failed to restore directory permissions after gaining write access:", err)
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
return fn(path)
|
|
}
|