mirror of
https://github.com/octoleo/syncthing.git
synced 2025-01-03 15:17:25 +00:00
lib/model: Fix child-check when deleting dirs in pull (#7236)
This commit is contained in:
parent
0f8290485e
commit
c48eb4241a
@ -1878,76 +1878,8 @@ func (f *sendReceiveFolder) deleteDirOnDisk(dir string, snap *db.Snapshot, scanC
|
||||
return err
|
||||
}
|
||||
|
||||
files, _ := f.mtimefs.DirNames(dir)
|
||||
|
||||
toBeDeleted := make([]string, 0, len(files))
|
||||
|
||||
var hasIgnored, hasKnown, hasToBeScanned, hasReceiveOnlyChanged bool
|
||||
|
||||
for _, dirFile := range files {
|
||||
fullDirFile := filepath.Join(dir, dirFile)
|
||||
switch {
|
||||
case fs.IsTemporary(dirFile) || f.ignores.Match(fullDirFile).IsDeletable():
|
||||
toBeDeleted = append(toBeDeleted, fullDirFile)
|
||||
continue
|
||||
case f.ignores != nil && f.ignores.Match(fullDirFile).IsIgnored():
|
||||
hasIgnored = true
|
||||
continue
|
||||
}
|
||||
cf, ok := snap.Get(protocol.LocalDeviceID, fullDirFile)
|
||||
switch {
|
||||
case !ok || cf.IsDeleted():
|
||||
// Something appeared in the dir that we either are not
|
||||
// aware of at all or that we think should be deleted
|
||||
// -> schedule scan.
|
||||
scanChan <- fullDirFile
|
||||
hasToBeScanned = true
|
||||
continue
|
||||
case ok && f.Type == config.FolderTypeReceiveOnly && cf.IsReceiveOnlyChanged():
|
||||
hasReceiveOnlyChanged = true
|
||||
continue
|
||||
}
|
||||
info, err := f.mtimefs.Lstat(fullDirFile)
|
||||
var diskFile protocol.FileInfo
|
||||
if err == nil {
|
||||
diskFile, err = scanner.CreateFileInfo(info, fullDirFile, f.mtimefs)
|
||||
}
|
||||
if err != nil {
|
||||
// Lets just assume the file has changed.
|
||||
scanChan <- fullDirFile
|
||||
hasToBeScanned = true
|
||||
continue
|
||||
}
|
||||
if !cf.IsEquivalentOptional(diskFile, f.modTimeWindow, f.IgnorePerms, true, protocol.LocalAllFlags) {
|
||||
// File on disk changed compared to what we have in db
|
||||
// -> schedule scan.
|
||||
scanChan <- fullDirFile
|
||||
hasToBeScanned = true
|
||||
continue
|
||||
}
|
||||
// Dir contains file that is valid according to db and
|
||||
// not ignored -> something weird is going on
|
||||
hasKnown = true
|
||||
}
|
||||
|
||||
if hasToBeScanned {
|
||||
return errDirHasToBeScanned
|
||||
}
|
||||
if hasIgnored {
|
||||
return errDirHasIgnored
|
||||
}
|
||||
if hasReceiveOnlyChanged {
|
||||
// Pretend we deleted the directory. It will be resurrected as a
|
||||
// receive-only changed item on scan.
|
||||
scanChan <- dir
|
||||
return nil
|
||||
}
|
||||
if hasKnown {
|
||||
return errDirNotEmpty
|
||||
}
|
||||
|
||||
for _, del := range toBeDeleted {
|
||||
f.mtimefs.RemoveAll(del)
|
||||
if err := f.deleteDirOnDiskHandleChildren(dir, snap, scanChan); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := f.inWritableDir(f.mtimefs.Remove, dir)
|
||||
@ -1966,6 +1898,99 @@ func (f *sendReceiveFolder) deleteDirOnDisk(dir string, snap *db.Snapshot, scanC
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *sendReceiveFolder) deleteDirOnDiskHandleChildren(dir string, snap *db.Snapshot, scanChan chan<- string) error {
|
||||
var dirsToDelete []string
|
||||
var hasIgnored, hasKnown, hasToBeScanned, hasReceiveOnlyChanged bool
|
||||
var delErr error
|
||||
|
||||
err := f.mtimefs.Walk(dir, func(path string, info fs.FileInfo, err error) error {
|
||||
if path == dir {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch match := f.ignores.Match(path); {
|
||||
case match.IsDeletable():
|
||||
if info.IsDir() {
|
||||
dirsToDelete = append(dirsToDelete, path)
|
||||
return nil
|
||||
}
|
||||
fallthrough
|
||||
case fs.IsTemporary(path):
|
||||
if err := f.mtimefs.Remove(path); err != nil && delErr == nil {
|
||||
delErr = err
|
||||
}
|
||||
return nil
|
||||
case match.IsIgnored():
|
||||
hasIgnored = true
|
||||
return nil
|
||||
}
|
||||
cf, ok := snap.Get(protocol.LocalDeviceID, path)
|
||||
switch {
|
||||
case !ok || cf.IsDeleted():
|
||||
// Something appeared in the dir that we either are not
|
||||
// aware of at all or that we think should be deleted
|
||||
// -> schedule scan.
|
||||
scanChan <- path
|
||||
hasToBeScanned = true
|
||||
return nil
|
||||
case ok && f.Type == config.FolderTypeReceiveOnly && cf.IsReceiveOnlyChanged():
|
||||
hasReceiveOnlyChanged = true
|
||||
return nil
|
||||
}
|
||||
diskFile, err := scanner.CreateFileInfo(info, path, f.mtimefs)
|
||||
if err != nil {
|
||||
// Lets just assume the file has changed.
|
||||
scanChan <- path
|
||||
hasToBeScanned = true
|
||||
return nil
|
||||
}
|
||||
if !cf.IsEquivalentOptional(diskFile, f.modTimeWindow, f.IgnorePerms, true, protocol.LocalAllFlags) {
|
||||
// File on disk changed compared to what we have in db
|
||||
// -> schedule scan.
|
||||
scanChan <- path
|
||||
hasToBeScanned = true
|
||||
return nil
|
||||
}
|
||||
// Dir contains file that is valid according to db and
|
||||
// not ignored -> something weird is going on
|
||||
hasKnown = true
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range dirsToDelete {
|
||||
if err := f.mtimefs.Remove(dirsToDelete[len(dirsToDelete)-1-i]); err != nil && delErr == nil {
|
||||
delErr = err
|
||||
}
|
||||
}
|
||||
|
||||
// "Error precedence":
|
||||
// Something changed on disk, check that and maybe all else gets resolved
|
||||
if hasToBeScanned {
|
||||
return errDirHasToBeScanned
|
||||
}
|
||||
// Ignored files will never be touched, i.e. this will keep failing until
|
||||
// user acts.
|
||||
if hasIgnored {
|
||||
return errDirHasIgnored
|
||||
}
|
||||
if hasReceiveOnlyChanged {
|
||||
// Pretend we deleted the directory. It will be resurrected as a
|
||||
// receive-only changed item on scan.
|
||||
scanChan <- dir
|
||||
return nil
|
||||
}
|
||||
if hasKnown {
|
||||
return errDirNotEmpty
|
||||
}
|
||||
// All good, except maybe failing to remove a (?d) ignored item
|
||||
return delErr
|
||||
}
|
||||
|
||||
// scanIfItemChanged schedules the given file for scanning and returns errModified
|
||||
// if it differs from the information in the database. Returns nil if the file has
|
||||
// not changed.
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@ -1350,6 +1351,32 @@ func TestPullDeleteCaseConflict(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullDeleteIgnoreChildDir(t *testing.T) {
|
||||
m, f := setupSendReceiveFolder(t)
|
||||
defer cleanupSRFolder(f, m)
|
||||
|
||||
parent := "parent"
|
||||
del := "ignored"
|
||||
child := "keep"
|
||||
matcher := ignore.New(f.mtimefs)
|
||||
must(t, matcher.Parse(bytes.NewBufferString(fmt.Sprintf(`
|
||||
!%v
|
||||
(?d)%v
|
||||
`, child, del)), ""))
|
||||
f.ignores = matcher
|
||||
|
||||
must(t, f.mtimefs.Mkdir(parent, 0777))
|
||||
must(t, f.mtimefs.Mkdir(filepath.Join(parent, del), 0777))
|
||||
must(t, f.mtimefs.Mkdir(filepath.Join(parent, del, child), 0777))
|
||||
|
||||
scanChan := make(chan string, 2)
|
||||
|
||||
err := f.deleteDirOnDisk(parent, f.fset.Snapshot(), scanChan)
|
||||
if err == nil {
|
||||
t.Error("no error")
|
||||
}
|
||||
}
|
||||
|
||||
func cleanupSharedPullerState(s *sharedPullerState) {
|
||||
s.mut.Lock()
|
||||
defer s.mut.Unlock()
|
||||
|
Loading…
Reference in New Issue
Block a user