lib/model: Fix checking children when trying to delete a dir (fixes #6646) (#6647)

This commit is contained in:
Simon Frei 2020-05-25 08:46:24 +02:00 committed by GitHub
parent cf75329067
commit c3b5eba205
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 68 additions and 34 deletions

View File

@ -64,8 +64,9 @@ var (
activity = newDeviceActivity() activity = newDeviceActivity()
errNoDevice = errors.New("peers who had this file went away, or the file has changed while syncing. will retry later") errNoDevice = errors.New("peers who had this file went away, or the file has changed while syncing. will retry later")
errDirPrefix = "directory has been deleted on a remote device but " errDirPrefix = "directory has been deleted on a remote device but "
errDirHasToBeScanned = errors.New(errDirPrefix + "contains unexpected files, scheduling scan") errDirHasToBeScanned = errors.New(errDirPrefix + "contains changed files, scheduling scan")
errDirHasIgnored = errors.New(errDirPrefix + "contains ignored files (see ignore documentation for (?d) prefix)") errDirHasIgnored = errors.New(errDirPrefix + "contains ignored files (see ignore documentation for (?d) prefix)")
errDirHasReceiveOnlyChanged = errors.New(errDirPrefix + "contains locally changed files")
errDirNotEmpty = errors.New(errDirPrefix + "is not empty; the contents are probably ignored on that remote device, but not locally") errDirNotEmpty = errors.New(errDirPrefix + "is not empty; the contents are probably ignored on that remote device, but not locally")
errNotAvailable = errors.New("no connected device has the required version of this file") errNotAvailable = errors.New("no connected device has the required version of this file")
errModified = errors.New("file modified but not rescanned; will try again later") errModified = errors.New("file modified but not rescanned; will try again later")
@ -1819,32 +1820,53 @@ func (f *sendReceiveFolder) deleteDirOnDisk(dir string, snap *db.Snapshot, scanC
toBeDeleted := make([]string, 0, len(files)) toBeDeleted := make([]string, 0, len(files))
hasIgnored := false var hasIgnored, hasKnown, hasToBeScanned, hasReceiveOnlyChanged bool
hasKnown := false
hasToBeScanned := false
for _, dirFile := range files { for _, dirFile := range files {
fullDirFile := filepath.Join(dir, dirFile) fullDirFile := filepath.Join(dir, dirFile)
if fs.IsTemporary(dirFile) || f.ignores.Match(fullDirFile).IsDeletable() { switch {
case fs.IsTemporary(dirFile) || f.ignores.Match(fullDirFile).IsDeletable():
toBeDeleted = append(toBeDeleted, fullDirFile) toBeDeleted = append(toBeDeleted, fullDirFile)
} else if f.ignores != nil && f.ignores.Match(fullDirFile).IsIgnored() { continue
case f.ignores != nil && f.ignores.Match(fullDirFile).IsIgnored():
hasIgnored = true hasIgnored = true
} else if cf, ok := snap.Get(protocol.LocalDeviceID, fullDirFile); !ok || cf.IsDeleted() || cf.IsInvalid() { continue
// Something appeared in the dir that we either are not aware of
// at all, that we think should be deleted or that is invalid,
// but not currently ignored -> schedule scan. The scanChan
// might be nil, in which case we trust the scanning to be
// handled later as a result of our error return.
if scanChan != nil {
scanChan <- fullDirFile
} }
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 hasToBeScanned = true
} else { continue
case ok && f.Type == config.FolderTypeReceiveOnly && cf.IsReceiveOnlyChanged():
hasReceiveOnlyChanged = true
continue
}
info, err := f.fs.Lstat(fullDirFile)
var diskFile protocol.FileInfo
if err == nil {
diskFile, err = scanner.CreateFileInfo(info, fullDirFile, f.fs)
}
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 // Dir contains file that is valid according to db and
// not ignored -> something weird is going on // not ignored -> something weird is going on
hasKnown = true hasKnown = true
} }
}
if hasToBeScanned { if hasToBeScanned {
return errDirHasToBeScanned return errDirHasToBeScanned
@ -1852,6 +1874,9 @@ func (f *sendReceiveFolder) deleteDirOnDisk(dir string, snap *db.Snapshot, scanC
if hasIgnored { if hasIgnored {
return errDirHasIgnored return errDirHasIgnored
} }
if hasReceiveOnlyChanged {
return errDirHasReceiveOnlyChanged
}
if hasKnown { if hasKnown {
return errDirNotEmpty return errDirNotEmpty
} }

View File

@ -818,9 +818,14 @@ func TestCopyOwner(t *testing.T) {
// owner/group. // owner/group.
dbUpdateChan := make(chan dbUpdateJob, 1) dbUpdateChan := make(chan dbUpdateJob, 1)
scanChan := make(chan string)
defer close(dbUpdateChan) defer close(dbUpdateChan)
f.handleDir(dir, f.fset.Snapshot(), dbUpdateChan, nil) f.handleDir(dir, f.fset.Snapshot(), dbUpdateChan, scanChan)
<-dbUpdateChan // empty the channel for later select {
case <-dbUpdateChan: // empty the channel for later
case toScan := <-scanChan:
t.Fatal("Unexpected receive on scanChan:", toScan)
}
info, err := f.fs.Lstat("foo/bar") info, err := f.fs.Lstat("foo/bar")
if err != nil { if err != nil {
@ -874,8 +879,12 @@ func TestCopyOwner(t *testing.T) {
SymlinkTarget: "over the rainbow", SymlinkTarget: "over the rainbow",
} }
f.handleSymlink(symlink, snap, dbUpdateChan, nil) f.handleSymlink(symlink, snap, dbUpdateChan, scanChan)
<-dbUpdateChan select {
case <-dbUpdateChan:
case toScan := <-scanChan:
t.Fatal("Unexpected receive on scanChan:", toScan)
}
info, err = f.fs.Lstat("foo/bar/sym") info, err = f.fs.Lstat("foo/bar/sym")
if err != nil { if err != nil {